/*
 * $Id: PDFImage.java,v 1.4 2002/04/05 13:01:40 ezb Exp $
 *
 * $Date: 2002/04/05 13:01:40 $
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package gnu.jpdf;

import java.awt.Image;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import java.util.zip.*;

/**
 * <p>This implements the Image XObject.  Calling one of the
 * <code>drawImage</code> methods of <code>PDFGraphics</code> will
 * put all the necessary code into the pdf file, and the image will
 * be encoded in ascii base 85, then deflated in zip format.</p>
 *
 * @author Eric Z. Beard (original version by Peter Mount)
 * @author Matthew Hreljac, mhreljac@hotmail.com
 * @author $Author: ezb $
 * @version $Revision: 1.4 $, $Date: 2002/04/05 13:01:40 $
 */
public class PDFImage extends PDFStream implements ImageObserver, Serializable
{
	private static final long serialVersionUID = 1L;
	/*
	 * NOTE: The original class is the work of Peter T. Mount, who released it
	 * in the uk.org.retep.pdf package.  It was modified by Eric Z. Beard as
	 * follows:
	 * The package name was changed to gnu.jpdf.
	 * The formatting was changed a little bit.
	 * Images were not yet implemented, so the core of this
	 *   class was mostly rewritten
	 * It is still licensed under the LGPL.
	 * Got some help with base85 methods from Mathew Hreljac
	 */


	// Dimensions of the object.
	private int objwidth;
	private int objheight;

	// Dimensions of the image.
	private int width;
	private int height;
	private Image img;
	private String name;

	/**
	 * Creates a new <code>PDFImage</code> instance.
	 *
	 */
	public PDFImage()
	{super("/XObject");}

	/**
	 * Creates a new <code>PDFImage</code> instance.
	 *
	 * @param img an <code>Image</code> value
	 */
	public PDFImage(Image img)
{
		this();
		setImage(img, 0, 0, img.getWidth(this), img.getHeight(this), this);
	}

	/**
	 * Creates a new <code>PDFImage</code> instance.
	 *
	 * @param img an <code>Image</code> value
	 * @param x an <code>int</code> value
	 * @param y an <code>int</code> value
	 * @param w an <code>int</code> value
	 * @param h an <code>int</code> value
	 * @param obs an <code>ImageObserver</code> value
	 */
	public PDFImage(Image img,int x,int y,int w,int h,ImageObserver obs) 
	{
		this();
		objwidth = w;
		objheight = h;
		setImage(img, x, y, img.getWidth(this), img.getHeight(this), obs);
	}

	/**
	 * Get the value of width.
	 * @return value of width.
	 */
	public int getWidth() 
	{return width;}

	/**
	 * Set the value of width.
	 * @param v  Value to assign to width.
	 */
	public void setWidth(int  v)
	{this.width = v;}

	/**
	 * Get the value of height.
	 * @return value of height.
	 */
	public int getHeight()
	{return height;}

	/**
	 * Set the value of height.
	 * @param v  Value to assign to height.
	 */
	public void setHeight(int  v)
	{this.height = v;}

	/**
	 * Set the name
	 *
	 * @param n a <code>String</code> value
	 */
	public void setName(String n)
	{name = n;}

	/**
	 * Get the name
	 *
	 * @return a <code>String</code> value
	 */
	public String getName() 
	{return name;}

	/**
	 * Set the image
	 *
	 * @param img an <code>Image</code> value
	 * @param x an <code>int</code> value
	 * @param y an <code>int</code> value
	 * @param w an <code>int</code> value
	 * @param h an <code>int</code> value
	 * @param obs an <code>ImageObserver</code> value
	 */
	public void setImage(Image img,int x,int y,int w,int h,ImageObserver obs) 
	{
		this.img = img;
		width  = w;
		height = h;
	}

	/**
	 * <p>Adobe's base 85 does not follow the format used by ipv6
	 * addresses.  It simply starts with 33 and goes straight up without
	 * skipping any characters</p>
	 *
	 * <p>Parts of this method contributed by Mathew Hreljac</p>
	 *
	 * @param stringToEncode a <code>String</code> value
	 * @return a <code>String</code> value
	 */
	private String base85Encoding(String stringToEncode)throws NumberFormatException 
	{
		if ((stringToEncode == null) || (stringToEncode.length() == 0))
			//System.out.println("PDFImage.base85Encoding() null or blank String");
			return "";
		if ((stringToEncode.length() > 8) ||((stringToEncode.length() % 2) != 0)) 
		{
			System.out.println("PDFImage.base85Encoding, Incorrect tuple length: " +
					stringToEncode.length());
			return "";
		}
		//System.out.println("str: " + stringToEncode);
		// String buffer to use to return the String encoding
		StringBuffer sb = new StringBuffer();

		// Deal with a partial tuple (less than 8 hex digits)
		// From Adobe's docs:
		// "Given n (1, 2 or 3) bytes of binary data, the encoding first
		// appends 4 - n zero bytes to make a complete 4-tuple.  This 4-tuple
		// is encoded in the usual way, but without applying the special
		// z-case.  Finally, only the first n+1 characters of the resulting
		// 5-tuple are written out.  Those characters are immediately followed
		// by the EOD marker, ~>"

		int numHexDigits = stringToEncode.length() / 2;
		int numAppendBytes = 4 - numHexDigits;
		for (int i = 0; i < numAppendBytes; i++)
			stringToEncode += "00";
		Vector<Integer> digitVector = new Vector<Integer>();
		long   number      = Long.parseLong(stringToEncode, 16);
		int    remainder   = 0;

		while (number >= 85) 
		{
			remainder = (int) (number % 85);
			number    = number / 85;
			digitVector.add( 0, new Integer( remainder ) );
		}
		digitVector.add( 0, new Integer( (int)number ) );

		for ( int i = 0; i < digitVector.size(); i++) 
		{
			char c = (char) (((Integer)digitVector.elementAt(i)).intValue() + 33);
			sb.append(c);
		}
		String tuple = sb.toString();
		int len = tuple.length();
		switch (len) {
		case 1: tuple = "!!!!" + tuple; break;
		case 2: tuple = "!!!" + tuple; break;
		case 3: tuple = "!!" + tuple; break;
		case 4: tuple = "!" + tuple; break;
		default: break;
		} // end switch
		//System.out.println("enc tuple: " + tuple);
		return (tuple);
	} // end base85encoding

	/**
	 * Writes the image to the stream
	 *
	 * @param os an <code>OutputStream</code> value
	 * @exception IOException if an error occurs
	 */
	public void writeStream(OutputStream os) throws IOException
	{
		// This is a non-deflated stream
		/*
    os.write("/Length ".getBytes());
    // Accout for stream\n ... >\nendstream
    os.write(Integer.toString(buf.size() + 18).getBytes());
    os.write("\n/Filter /ASCII85Decode".getBytes());
    os.write("\n>>\nstream\n".getBytes());
    buf.writeTo(os);
    os.write(">\nendstream\nendobj\n\n".getBytes());
		 */
		ByteArrayOutputStream b = new ByteArrayOutputStream();
		DeflaterOutputStream dos = new DeflaterOutputStream(b);
		buf.writeTo(dos);
		dos.finish();
		dos.close();

		// FlatDecode is compatible with the java.util.zip.Deflater class
		//os.write("/Filter [/FlateDecode /ASCIIHexDecode]\n".getBytes());
		os.write("/Filter [/FlateDecode /ASCII85Decode]\n".getBytes());
		os.write("/Length ".getBytes());
		os.write(Integer.toString(b.size()).getBytes());
		os.write("\n>>\nstream\n".getBytes());
		b.writeTo(os);
		os.write("\nendstream\nendobj\n".getBytes());
	} // end writeStream

	/**
	 * <p>Compression needs to be improved here</p>
	 *
	 * @param os OutputStream to send the object to
	 * @exception IOException on error
	 */
	public void write(OutputStream os) throws IOException
	{
		writeStart(os);

		// write the extra details
		os.write("/Subtype /Image\n/Name ".getBytes());
		os.write(name.getBytes());
		os.write("\n/Width ".getBytes());
		os.write(Integer.toString(width).getBytes());
		os.write("\n/Height ".getBytes());
		os.write(Integer.toString(height).getBytes());
		os.write("\n/BitsPerComponent 8\n/ColorSpace /DeviceRGB\n".getBytes());

		// write the pixels to the stream
		//System.err.println("Processing image "+width+"x"+height+" pixels");
		ByteArrayOutputStream bos = getStream();

		int w = width;
		int h = height;
		int x = 0;
		int y = 0;
		int[] pixels = new int[w * h];
		PixelGrabber pg = new PixelGrabber(img, x, y, w, h, pixels, 0, w);
		try 
		{
			pg.grabPixels();
		} 
		catch (InterruptedException e) 
		{
			System.err.println("interrupted waiting for pixels!");
			return;
		}
		if ((pg.getStatus() & ImageObserver.ABORT) != 0) 
		{
			System.err.println("image fetch aborted or errored");
			return;
		}
		StringBuffer out = new StringBuffer();
		for (int j = 0; j < h; j++) 
		{
			for (int i = 0; i < w; i++) 
			{
				//System.out.print("p[" + j * w + i+ "]=" + pixels[j * w + i] + ".");
				out.append(handlePixel(x+i, y+j, pixels[j * w + i]));
				if (out.toString().length() >= 8) 
				{
					String tuple = out.substring(0, 8);
					out.delete(0, 8);
					// Convert !!!!! to 'z'
					String encTuple = base85Encoding(tuple);
					if (encTuple.equals("!!!!!"))
						encTuple = "z";
					bos.write(encTuple.getBytes());
				}
			}
		}
		// This should be the only partial tuple case,

		String lastTuple = base85Encoding(out.toString());
		//System.out.println("lastTuple: " + lastTuple);
		bos.write(lastTuple.getBytes());
		bos.write("~".getBytes());

		//System.out.println("Processing done");

		// this will write the actual stream
		setDeflate(false);
		writeStream(os);
		// Note: we do not call writeEnd() on streams!
	}

	/**
	 * <p>Converts a pixel to a hex string</p>
	 *
	 * @param x an <code>int</code> value
	 * @param y an <code>int</code> value
	 * @param p an <code>int</code> value
	 * @return a <code>String</code> value
	 */
	public static String handlePixel(int x, int y, int p) 
	{
		int alpha = (p >> 24) & 0xff;
		int red   = (p >> 16) & 0xff;
		int green = (p >>  8) & 0xff;
		int blue  = (p      ) & 0xff;
		String redHex = Integer.toHexString(red);
		String greenHex =  Integer.toHexString(green);
		String blueHex =  Integer.toHexString(blue);
		if (redHex.length() == 1)
			redHex = "0" + redHex;
		if (greenHex.length() == 1)
			greenHex = "0" + greenHex;
		if (blueHex.length() == 1)
			blueHex = "0" + blueHex;
		return redHex + greenHex + blueHex;
	} // end handlePixel

	/**
	 * Describe <code>imageUpdate</code> method here.
	 *
	 * @param img an <code>Image</code> value
	 * @param infoflags an <code>int</code> value
	 * @param x an <code>int</code> value
	 * @param y an <code>int</code> value
	 * @param w an <code>int</code> value
	 * @param h an <code>int</code> value
	 * @return a <code>boolean</code> value
	 */
	public boolean imageUpdate(Image img,int infoflags,int x,int y,int w,int h)
	{
		System.err.println("img="+img+"\ninfoflags="+infoflags+
				"\nx="+x+" y="+y+" w="+w+" h="+h);
		//if(img == this.img) {
		if(infoflags==ImageObserver.WIDTH)
			width = w;
		if(infoflags==ImageObserver.HEIGHT)
			height = h;
		//return true;
		//}
		return false;
	}
} // end class PDFImage